/**
 * 
 */
package com.module.bp.player;

import com.dalonedrau.jogl.engine.GameApplet;
import com.dalonedrau.jogl.engine.GlobalConsts;
import com.dalonedrau.jogl.engine.Interactive;
import com.dalonedrau.vectors.Vector2;
import com.dalonedrau.watchable.Watchable;
import com.dalonedrau.watchable.Watcher;
import com.module.bp.bus.BPHexMap;
import com.rpg.base.Party;
import com.rpg.base.interactive.flyweight.BaseInteractiveObject;

/**
 * @author Donald
 */
public final class BPParty implements Party<BPCharacter>, Watchable {
	/** the singleton instance of {@link BPParty}. */
	private static BPParty	instance;
	/**
	 * Gets the singleton instance of {@link BPParty}.
	 * @return {@link BPParty}
	 */
	public static BPParty getInstance() {
		if (BPParty.instance == null) {
			BPParty.instance = new BPParty();
		}
		return BPParty.instance;
	}
	/** the current hex location. */
	private Vector2			currentHex;
	/** the amount of food the party has. */
	private int				food;
	/** the amount of gold the party has. */
	private long			gold;
	/** the list of items the party has found. */
	private int				items[]		= new int[0];
	/** the list of all party members. */
	private int[]			members		= new int[0];
	/** the number of members in the party. */
	private int				numMembers	= 0;
	/** the number of mounts in the party. */
	private int				numMounts;
	/**
	 * the number of moves the party can make at the beginning of each turn; 
	 * this is reset at the beginning of every turn.
	 */
	private int				numMovesPossible;
	/**
	 * the number of moves the party has taken; 
	 * this is reset at the beginning of every turn.
	 */
	private int				numMovesTaken;
	/** the number of winged mounts in the party. */
	private int				numWingedMounts;
	/** flag indicating the party has a raft. */
	private boolean			raft		= false;
	/** the list of {@link Watcher}s. */
	private Watcher[]		watchers	= new Watcher[0];
	private BPPartyWMSprite	worldMapSprite;
	/** Hidden constructor. */
	private BPParty() {
		worldMapSprite = new BPPartyWMSprite();
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addGold(final long value) {
		gold += value;
		notifyWatchers();
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addMember(final BPCharacter pc) {
		if (pc != null) {
			pc.removeIOFlag(BPConsts.IO_NPC);
			pc.addIOFlag(BPConsts.IO_PC);
			this.addMember(pc.getRefId());
			pc.onAddToParty();
		}
		notifyWatchers();
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addMember(final int ioId) {
		if (members.length <= numMembers) {
			int[] dest = new int[numMembers + 1];
			System.arraycopy(members, 0, dest, 0, members.length);
			members = dest;
			dest = null;
		}
		members[numMembers] = ioId;
		numMembers++;
		notifyWatchers();
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addWatcher(final Watcher watcher) {
		Watcher[] dest = new Watcher[watchers.length + 1];
		System.arraycopy(watchers, 0, dest, 0, watchers.length);
		dest[watchers.length] = watcher;
		watchers = dest;
		dest = null;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void becomesDead() {
		// TODO Auto-generated method stub
		notifyWatchers();

	}
	/**
	 * Determines if the party can travel by flying.
	 * @return if true, the party can fly for a day; false otherwise
	 */
	public boolean canTravelFlying() {
		boolean can = true;
		// are there enough mounts for everyone
		int needsMount = 0;
		// count every pc who can ride
		try {
			for (int i = 0; i < members.length; i++) {
				BPCharacter io =
						(BPCharacter) Interactive.getInstance().getIO(
								members[i]);
				if (!io.hasFlag(BPConsts.IO_INNATE_FLYING)) {
					needsMount++;
				}
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
		if (numWingedMounts < needsMount) {
			can = false;
		} else {
			// each mount can carry 30 loads
			// add up the load capacity
			int w = getWeightCarried();
			w += getPartyRidingLoad();
			if (w > numWingedMounts * 30) {
				can = false;
			}
		}
		// will party be leaving non-winged mounts behind?
		if (numMounts > 0) {
			can = false;
		}
		return can;
	}
	/**
	 * Determines if the party can travel mounted.
	 * @return if true, the party can ride for a day; false otherwise
	 */
	public boolean canTravelMounted() {
		boolean can = true;
		// are there enough mounts for everyone
		int needsMount = 0;
		// count every pc who can ride
		try {
			for (int i = 0; i < members.length; i++) {
				BPCharacter io =
						(BPCharacter) Interactive.getInstance().getIO(
								members[i]);
				if (!io.hasFlag(BPConsts.IO_INNATE_FLYING)
						&& !io.hasFlag(BPConsts.IO_INNATE_MOUNTED)) {
					needsMount++;
				}
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
		if (numMounts + numWingedMounts < needsMount) {
			can = false;
		} else {
			// each mount can carry 30 loads
			// add up the load capacity
			int w = getWeightCarried();
			w += getPartyRidingLoad();
			if (w > (numMounts + numWingedMounts) * 30) {
				can = false;
			}
		}
		return can;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void gameReset() {
		food = 0;
		gold = 0;
		members = new int[0];
		items = new int[0];
		numMembers = 0;
		numMounts = 0;
		numWingedMounts = 0;
		notifyWatchers();
	}
	/**
	 * Gets the current hex the party occupies.
	 * @return {@link Vector2}
	 */
	public Vector2 getCurrentHex() {
		return currentHex;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public BPCharacter[] getMembers() {
		BPCharacter[] list = new BPCharacter[members.length];
		try {
			for (int i = 0; i < members.length; i++) {
				list[i] =
						(BPCharacter) Interactive.getInstance().getIO(
								members[i]);
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
		return list;
	}
	/**
	 * Gets the mounts in the party.
	 * @return <code>int</code>
	 */
	public int getNumberOfMounts() {
		return numMounts;
	}
	/**
	 * Gets the winged mounts in the party.
	 * @return <code>int</code>
	 */
	public int getNumberOfWingedMounts() {
		return numWingedMounts;
	}
	/**
	 * Gets a list of all party members that can act as local guides for a
	 * specific location. If noone can act as a guide, the list is empty.
	 * @param vertex the map location
	 * @return <code>int</code>[]
	 */
	public int[] getNumGuidesForArea(final int vertex) {
		int[] list = new int[0];
		try {
			for (int i = 0; i < members.length; i++) {
				BaseInteractiveObject io =
						Interactive.getInstance().getIO(members[i]);
				if (io.hasIOFlag(BPConsts.IO_LOCAL_GUIDE)
						&& ((BPCharacter) io).isGuideForLocation(vertex)) {
					int[] dest = new int[list.length + 1];
					System.arraycopy(list, 0, dest, 0, list.length);
					dest[list.length] = members[i];
					list = dest;
					dest = null;
				}
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
		return list;
	}
	/**
	 * Gets the number of moves that are possible in a turn.
	 * @return <code>int</code>
	 */
	public int getNumMovesPossible() {
		return numMovesPossible;
	}
	/**
	 * Gets the number of moves the party has taken this turn.
	 * @return <code>int</code>
	 */
	public int getNumMovesTaken() {
		return numMovesTaken;
	}
	/**
	 * Gets the total weight the party can carry.
	 * @return <code>int</code>
	 */
	public int getPartyCarryingLoad() {
		int w = 0;
		try {
			// every pc who can ride counts as 20 loads
			for (int i = 0; i < members.length; i++) {
				BPCharacter io =
						(BPCharacter) Interactive.getInstance().getIO(
								members[i]);
				if (!io.hasFlag(BPConsts.IO_INNATE_FLYING)
						&& !io.hasFlag(BPConsts.IO_INNATE_MOUNTED)
						&& !io.hasFlag(BPConsts.IO_INCAPACITATED)) {
					w += 10;
				}
			}
			w += (numMounts + numWingedMounts) * 30;
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
		return w;
	}
	/**
	 * Gets the food the party has.
	 * @return <code>int</code>
	 */
	public final int getPartyFood() {
		return food;
	}
	/**
	 * Gets the party's daily food obligations.
	 * @return <code>int</code>
	 */
	public int getPartyFoodObligation() {
		int f = 0;
		if (currentHex != null) {
			try {
				// every pc who can ride counts as 20 loads
				for (int i = 0; i < members.length; i++) {
					BPCharacter io =
							(BPCharacter) Interactive.getInstance().getIO(
									members[i]);
					f++;
				}
				// check the party location. if party mounts cannot forage,
				// mounts require 2 food units per day
				int currentLocType = BPHexMap.getInstance().getHexType(
						currentHex);
				switch (currentLocType) {
				case BPConsts.HEX_COUNTRY:
				case BPConsts.HEX_FARM:
				case BPConsts.HEX_FOREST:
				case BPConsts.HEX_HILLS:
					break;
				default:
					f += numMounts * 2;
					f += numWingedMounts * 2;
					break;
				}
				// food requirement doubles in desert with no oasis due to need
				// for
				// water
				if (currentLocType == BPConsts.HEX_DESERT_NO_OASIS) {
					f *= 2;
				}
			} catch (Exception ex) {
				JOGLErrorHandler.getInstance().fatalError(ex);
			}
		}
		return f;
	}
	/**
	 * Gets the party's gold.
	 * @return <code>long</code>
	 */
	public final long getPartyGold() {
		return gold;
	}
	/**
	 * Gets the party's daily payroll.
	 * @return <code>int</code>
	 */
	public int getPartyPayroll() {
		int p = 0;
		try {
			// every pc who can ride counts as 20 loads
			for (int i = 0; i < members.length; i++) {
				BPCharacter io =
						(BPCharacter) Interactive.getInstance().getIO(
								members[i]);
				if (io.hasFlag(BPConsts.IO_SALARIED)) {
					p += io.getSalary();
				}
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
		return p;
	}
	/**
	 * Gets the total weight load for all party members who can ride.
	 * @return <code>int</code>
	 */
	public int getPartyRidingLoad() {
		int w = 0;
		try {
			// every pc who can ride counts as 20 loads
			for (int i = 0; i < members.length; i++) {
				BPCharacter io =
						(BPCharacter) Interactive.getInstance().getIO(
								members[i]);
				if (!io.hasFlag(BPConsts.IO_INNATE_FLYING)
						&& !io.hasFlag(BPConsts.IO_INNATE_MOUNTED)
						&& !io.hasFlag(BPConsts.IO_INCAPACITATED)) {
					w += 20;
				}
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
		return w;
	}
	/**
	 * Gets the total weight the party is carrying.
	 * @return <code>int</code>
	 */
	public int getWeightCarried() {
		int w = 0;
		// each food unit costs 1 load
		w += food;
		// each fraction of 100 gold costs 1 load
		w += Math.round(gold / 100f);
		// each party member being carried costs 20 loads
		try {
			for (int i = 0; i < members.length; i++) {
				BPCharacter io =
						(BPCharacter) Interactive.getInstance().getIO(
								members[i]);
				if (io.hasFlag(BPConsts.IO_INCAPACITATED)) {
					w += 20;
				}
			}
			// count items carried
			for (int i = 0; i < items.length; i++) {
				// ****************************
				// TO DO
				// ****************************
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
		return w;
	}
	public BaseInteractiveObject getWorldMapSprite() {
		return worldMapSprite;
	}
	/**
	 * Determines if the party has a raft.
	 * @return true if the party has a raft; false otherwise
	 */
	public boolean hasRaft() {
		return raft;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void initializeParty() {
		gameReset();
	}
	/**
	 * Determines if the party is in a castle hex.
	 * @return true if the party is in a castle hex; false otherwise
	 */
	public boolean isInCastleHex() {
		boolean is = false;
		int type = BPHexMap.getInstance().getHexType(currentHex);
		if (type == BPConsts.HEX_CASTLE) {
			is = true;
		}
		return is;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isInFightMode() {
		// TODO Auto-generated method stub
		return false;
	}
	/**
	 * Determines if the party is in a ruins hex.
	 * @return true if the party is in a ruins hex; false otherwise
	 */
	public boolean isInRuinsHex() {
		boolean is = false;
		int type = BPHexMap.getInstance().getHexType(currentHex);
		if (type == BPConsts.HEX_RUINS_DESERT_OASIS
				|| type == BPConsts.HEX_RUINS_HILLS
				|| type == BPConsts.HEX_RUINS_MOUNTAINS) {
			is = true;
		}
		return is;
	}
	/**
	 * Determines if the party is in a temple hex.
	 * @return true if the party is in a temple hex; false otherwise
	 */
	public boolean isInTempleHex() {
		boolean is = false;
		int type = BPHexMap.getInstance().getHexType(currentHex);
		if (type == BPConsts.HEX_TEMPLE_COUNTRY
				|| type == BPConsts.HEX_TEMPLE_MOUNTAINS
				|| type == BPConsts.HEX_TEMPLE_DESERT
				|| type == BPConsts.HEX_TEMPLE_FOREST) {
			is = true;
		}
		return is;
	}
	/**
	 * Determines if the party is in a town hex.
	 * @return true if the party is in a town hex; false otherwise
	 */
	public boolean isInTownHex() {
		boolean is = false;
		int type = BPHexMap.getInstance().getHexType(currentHex);
		if (type == BPConsts.HEX_TOWN_COUNTRY
				|| type == BPConsts.HEX_TOWN_FARM) {
			is = true;
		}
		return is;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void keyringAdd(long key) {
		// TODO Auto-generated method stub

	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void keyringCombine(BaseInteractiveObject io) {
		// TODO Auto-generated method stub

	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void keyringInit() {
		// TODO Auto-generated method stub

	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void notifyWatchers() {
		for (int i = 0; i < watchers.length; i++) {
			watchers[i].watchUpdated(this);
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void questAdd(final long quest) {
		// TODO Auto-generated method stub

	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void questInit() {
		// TODO Auto-generated method stub

	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeWatcher(final Watcher watcher) {
		Watcher[] dest = new Watcher[watchers.length - 1];
		for (int i = 0, j = 0; i < watchers.length; i++) {
			if (!watchers[i].equals(watcher)) {
				dest[j] = watchers[i];
				j++;
			}
		}
		watchers = dest;
		dest = null;
	}
	/**
	 * Resets the number of moves the party has left at the beginning of each
	 * turn.
	 */
	public void resetNumberOfMoves() {
		numMovesPossible = 1;
		if (canTravelMounted()) {
			numMovesPossible = 2;
		}
		if (canTravelFlying()) {
			numMovesPossible = GlobalConsts.INT_003;
		}
		numMovesPossible = 3;
		numMovesTaken = 0;
	}
	/**
	 * Sets the current hex the party occupies.
	 * @param x the x position
	 * @param y the y position
	 */
	public void setCurrentHex(final int x, final int y) {
		if (currentHex == null) {
			currentHex = new Vector2(x, y);
		} else {
			currentHex.set(x, y);
		}
		// move the party sprite
		// get the hex' center point.
		Vector2 v = BPHexMap.getInstance().getHexCenterPoint(x, y);
		// move left, down 1/2 the sprite's width
		v.setX(v.getX() - worldMapSprite.getSprite().getWidth() / 2);
		v.setY(v.getY() - worldMapSprite.getSprite().getHeight() / 2);
		worldMapSprite.getSprite().setPosition(
				v.getX(),
				v.getY(),
				worldMapSprite.getSprite().getPosition().getZ()
				);
		notifyWatchers();
	}
	/**
	 * Sets the current hex the party occupies.
	 * @param loc the currentHex to set
	 */
	public void setCurrentHex(final Vector2 loc) {
		this.setCurrentHex((int) loc.getX(), (int) loc.getY());
	}
	/**
	 * Sets the flag indicating the party has a raft.
	 * @param val the new flag
	 */
	public void setHasRaft(final boolean val) {
		raft = val;
	}
	/**
	 * Sets the number of moves the party has taken.
	 * @param val the number of moves the party has taken
	 */
	public void setNumMovesTaken(final int val) {
		numMovesTaken = val;
	}
	public void setWorldMapSpriteWalking(final int direction, 
			final long flags) {
		worldMapSprite.onOverworldMapWalk(direction, flags);
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void startNewQuest() {
		// TODO Auto-generated method stub

	}
}
